# -*- coding: utf-8 -*-
"""
Created on Mon May 31 23:39:19 2021

@author: ZHOU
"""

# -*- coding: utf-8 -*-
 
import tkinter as tk # 调用窗口tk
from tkinter import ttk
from tkinter.filedialog import *
import tkinter.messagebox
from PIL import Image, ImageTk # 调用图像处理库pillow
import api_face # 调用本地函数库 用于登入外部机器学习库并调用人脸识别函数
import cv2 # 调用OpenCV图像处理库
import threading # 调用threading多线程运行库
import time # 调用系统时间戳库

import matplotlib.pyplot as plt # 调用matplotlib绘图库
plt.rcParams['font.sans-serif'] = ['SimHei'] # 载入字体

#print('请输入需录入的人脸图片路径/文件名：')
#pic_name=input()

# 利用matplotlib显示图片函数  
def pshow(words,picture):
    plt.imshow(picture[:,:,::-1])   # 将读取的图片以RGB转换为BGR
    plt.title(words), plt.xticks([]), plt.yticks([])
    plt.show() # 显示图片
    
    
class Login(ttk.Frame): # 定义窗口大类
    def __init__(self, win):
        ttk.Frame.__init__(self, win)
        frame0 = ttk.Frame(self)
        frame1 = ttk.Frame(self)
        win.title("人脸识别")
        win.minsize(1240, 620)
        self.center_window()    # 执行中置窗口函数
        self.thread_run = None 	# 赋值 线程1默认关闭
        self.thread_run2 = None 	# 线程2默认关闭
        self.camera = None 	# 摄像头默认关闭
 
        #定义tk窗口属性
        
        #self.pilImage = Image.open("img/start.png")
        #self.tkImage = ImageTk.PhotoImage(image=self.pilImage)
        #self.image_ctl = tk.Label(frame0, image=self.tkImage)
        #self.image_ctl.pack()
 
        frame0.pack(side=TOP, fill=tk.Y, expand=1)
        frame1.pack(side=TOP, fill=tk.Y, expand=1)
 
        self.facer = ttk.Label(frame1, text='', font=('Times', '20')) # 字体
        self.facer.pack()
        
        def filefound(): # 定义获取图片路径的函数
            filepath= askopenfilename() # 获取文件路径
            pic_name=filepath
            self.pic_path2 = pic_name # 赋值给图像2
            pic_img=cv2.imread(self.pic_path2)
            pshow('所选人像图片',pic_img) # 显示所选图片
            pic_xz = pic_img.shape # 计算图像大小
            pic_h=pic_xz[0] # 得出图片高度
            pic_w=pic_xz[1] # 得出图片宽度
            turn_w=pic_w*500/pic_h # 限制最大高度为580 以防窗口过小不完全显示 等比例转换宽度
            turn_w=int(turn_w)
            print('人像图像大小（高 宽）：',pic_h,pic_w)
            print ('路径：',filepath)
            # 在tk窗口中显示所选图片
            self.pilImage = Image.open(self.pic_path2)            
            self.photo = self.pilImage.resize((turn_w,500)) # 限制最大高度为580 等比缩放显示
            self.tkImage = ImageTk.PhotoImage(image=self.photo)
            self.image_ctl = tk.Label(frame0, image=self.tkImage)
            self.image_ctl.pack()
            #e.delete(0, END)  # 将输入框里面的内容清空
            #e.insert(0, filepath)
        
        #button2=Button(frame1,text="button2",command=filefound).grid(row=0,column=3)
        # 按钮1 调用filefound函数 获取选择图片的路径 并赋值给self.pic_path2 输出图像
        self.face_button1 = ttk.Button(frame1, text="1. 选择人像图片", width=15, command=filefound)
        self.face_button1.pack(side=TOP)        
        # 按钮2 调用摄像头函数
        self.url_face_button = ttk.Button(frame1, text="2. 使用相机识别", width=15, command=self.cv_face)
        self.url_face_button.pack(side=TOP)
        #self.file_pic_button = ttk.Button(frame1, text="本地文件识别", width=15, command=self.file_pic)
        #self.file_pic_button.pack(side=TOP)
 
        self.pack(fill=tk.BOTH, expand=tk.YES, padx="10", pady="10")
 
    #使弹出的窗体处于屏幕的中间位置
    def center_window(self):
        screenwidth = log.winfo_screenwidth()	# 获取屏幕分辨率宽
        screenheight = log.winfo_screenheight()	# 获取屏幕分辨率高
        log.update()	# 更新窗口
        width = log.winfo_width()	# 重新赋值
        height = log.winfo_height()
        size = '+%d+%d' % ((screenwidth - width)/2, (screenheight - height)/2)
        # 重新赋值大小 大小为屏幕大小/2
        log.geometry(size) 	# 以新大小定义窗口
 
#    def file1(self):
#        self.pic_path = askopenfilename(title="选择识别图片", filetypes=[("jpg图片", "*.jpg"), ("png图片", "*.png")])
 
    def cv_face(self):   # 调用摄像头函数
        if self.thread_run:
            if self.camera.isOpened(): # 如果已经打开则关闭
                self.camera.release()
                print("关闭摄像头")
                self.camera = None
                self.thread_run = False
            return
        if self.camera is None: # 如果没有摄像头则尝试打开
            self.camera = cv2.VideoCapture(1) # 利用OpenCV调用外摄像头
            if not self.camera.isOpened(): # 如果没有打开 则调用内摄像头
                self.camera = None
                print("没有外置摄像头")
                self.camera = cv2.VideoCapture(0) # 用OpenCV调用内摄像头
                if not self.camera.isOpened(): # 如果没有打开 则打开失败
                    print("没有内置摄像头")
                    tkinter.messagebox.showinfo('警告', '摄像头打开失败！')
                    self.camera = None
                    return
                else:
                    print("打开内置摄像头")
            else:
                print("打开外置摄像头")
        self.thread = threading.Thread(target=self.video_thread) # 多线程函数执行摄像头运行函数
        self.thread.setDaemon(True)
        self.thread.start()
        self.thread_run = True
 
    def video_thread(self): # 开始摄像头运行
        self.thread_run = True # 多线程1开启
        self.thread2 = threading.Thread(target=self.video_pic)
        self.thread2.setDaemon(True)
        self.thread2.start()
        self.thread_run2 = True
        while self.thread_run: # 循环一直调用摄像头
            _, img_bgr = self.camera.read() # 以bgr格式读取摄像头内的截图 
            gray = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2GRAY) # 灰度转换
            # 在CV官方机器学习库内加载人脸识别分类器                
            # Python\Python38-32\Lib\site-packages\cv2\data  这个目录下也有
            classifier = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
            color = (0,255,0) # 绿色线
            # 识别器进行识别
            faceRects = classifier.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=3,minSize=(32, 32))
            # 识别器返回一个列表, 里面是每个识别出的人脸的区域, 左上和右下定点的坐标
            # print(faceRects)  #[[113  42  60  60]]    前两个值是左上定点的xy坐标,第三个是width 宽度对应y的变化, 另一个就是x的

            # 判断识别结果集合长度
            if len(faceRects):
                for faceRect in faceRects:
                    x,y,w,h = faceRect
                    # 用矩形框选出人脸   最后一个参数2是框线宽度
                    cv2.rectangle(img_bgr,(x, y), (x + h, y + w), color, 2)                 
            img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # 颜色转换
            im = Image.fromarray(img)
            w, h = im.size
            pil_image_resized = self.resize(w, h, im) # 调整大小函数
            self.imgtk = ImageTk.PhotoImage(image=pil_image_resized)
            self.image_ctl.configure(image=self.imgtk)
        print("结束运行")
 
    def video_pic(self): # 视频截图保存及框选函数
        self.thread_run2 = True     # 开启多线程
        predict_time = time.time() # 获得系统时间
        while self.thread_run2: # 循环读取
            if time.time() - predict_time > 2: #每2s读取一次摄像头截图
                print("正在识别中")
                _, img_bgr = self.camera.read() # 重新读取摄像头图像
                cv2.imwrite("tmp/test.jpg", img_bgr) #利用cv写入到tmp/test.jpg路径下
                test_pic=cv2.imread('tmp/test.jpg') # 重新读取截图
                pshow('识别截图',test_pic) # 显示截图
                
                # 图像路径 我用的相对路径
                face_mark = 'tmp/test.jpg'
                # 读取截图
                faceImg = cv2.imread(face_mark)
                # 转换灰色
                gray = cv2.cvtColor(faceImg,cv2.COLOR_RGB2GRAY) # 由于tmp/test.jpg路径下的已经转换成RGB保存 所以不用再进行BGR转换

                # 在CV官方机器学习库内加载人脸识别分类器                
                # Python\Python38-32\Lib\site-packages\cv2\data  这个目录下也有
                classifier = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
                color = (0,255,0) # 绿色线

                # 识别器进行识别
                faceRects = classifier.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=3,minSize=(32, 32))
                # 识别器返回一个列表, 里面是每个识别出的人脸的区域, 左上和右下定点的坐标
                # print(faceRects)  #[[113  42  60  60]]    前两个值是左上定点的xy坐标,第三个是width 宽度对应y的变化, 另一个就是x的

                # 判断识别结果集合长度
                if len(faceRects):
                    for faceRect in faceRects:
                        x,y,w,h = faceRect
                        # 用矩形框选出人脸   最后一个参数2是框线宽度
                        cv2.rectangle(faceImg,(x, y), (x + h, y + w), color, 2)
        
                
                pshow('识别结果',faceImg)    # 输出框选结果图像并显示
                cv2.imwrite("tmp/test2.jpg", faceImg)
                self.pic_path = "tmp/test.jpg" # 重新读取摄像头截图
#                self.pilImage = Image.open(self.pic_path)
#                self.tkImage = ImageTk.PhotoImage(image=self.pilImage)
#                self.image_ctl = tk.Label(frame0, image=self.tkImage)
#                self.image_ctl.pack()
                try:
                    self.file_pic() #执行识别函数
                except:
                    pass
                predict_time = time.time()      # 读取时间          
                print("识别结束")
                
                # 看门狗程序（调试用）
                # 防止程序关闭时进入死循环跑飞
                #print('请输入任意值以继续，否则请关闭窗口以终止程序：')
                #a=input()
                #print(a)
        pass
 
    def file_pic(self): #识别函数
        # self.pic_path2在按钮1中被赋值  self.pic_path为调用摄像头的图像
        facestr, result = api_face.facef(self.pic_path, self.pic_path2) # 调用api_face库的人脸识别函数
        self.facer.configure(text=str(facestr))
        #self.pic() # 对摄像头图像进行尺度变换
        if result > 80: #识别结果大于80
            tkinter.messagebox.showinfo('提示', '人脸匹配成功！') # tk窗口提示
            print('人像图片路径：'+self.pic_path2) # 输出人像文件路径
            try:
                f=open("识别记录.txt","r")
                fi=open("识别记录.txt","a")
                txt=time.ctime()
                fi.write(txt+'   人像图片路径：   '+self.pic_path2+"     人脸匹配成功！ \n")
                f.close()
                fi.close() # 将识别成功的记录保存在txt文件下
            except:
                f=open("识别记录.txt","w")
                txt=time.ctime()
                f.write(txt+'   人像图片路径：   '+self.pic_path2+"     人脸匹配成功！ \n")
                f.close()
                
            
            
            # close_window()
            # os.system("python3 ./main.py")
        #if result < 20:
        #    tkinter.messagebox.showinfo('提示', '未检测到人脸！')
        else: # 小于80失败
            tkinter.messagebox.showinfo('提示', '人脸匹配失败！')
        
 
#    def pic(self): # 对摄像头图像进行尺度变换
#        self.pilImage3 = Image.open(self.pic_path) # 用pillow读取摄像头图像
#        w, h = self.pilImage3.size # 计算大小赋值给宽 高
#        pil_image_resized = self.resize(w, h, self.pilImage3) # 调整大小函数
#        self.tkImage3 = ImageTk.PhotoImage(image=pil_image_resized) 
#        self.image_ctl.configure(image=self.tkImage3) # 输出结果
 
    def resize(self, w, h, pil_image): # 调整大小函数
        w_box = 1000 # 定义最大宽度
        h_box = 500 # 最大高度
        f1 = 1.0*w_box/w # 最大值/真实值
        f2 = 1.0*h_box/h
        factor = min([f1, f2]) # 取最小值
        width = int(w*factor) # 用最小值*对应值 调整到最大定义值 等比调整另一个值
        height = int(h*factor)
        return pil_image.resize((width, height), Image.ANTIALIAS) # 输出调整
 
 
def close_window():
    print("已关闭人脸识别")
    if Login.thread_run:
        Login.thread_run = False
        Login.thread.join(2.0)
    log.destroy()
    

    
if __name__ == '__main__':
    log = tk.Tk()
 
    login = Login(log)
    # close,退出输出destroy
    log.protocol('清除窗口', close_window)
    # 进入消息循环
    log.mainloop()
    
    
